// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#include "runtime/io/error-converter.h"

#include "gutil/strings/substitute.h"
#include "util/debug-util.h"
#include "util/error-util.h"
#include "util/string-parser.h"

#include "common/names.h"

namespace impala {

using std::unordered_map;

unordered_map<int, string> ErrorConverter::errno_to_error_text_map_(
    {{EACCES, "Access denied for the process' user"},
     {EINTR,   "Internal error occured."},
     {EINVAL,  "Invalid inputs."},
     {EMFILE,  "Process level opened file descriptor count is reached."},
     {ENAMETOOLONG,
         "Either the path length or a path component exceeds the maximum length."},
     {ENFILE,  "OS level opened file descriptor count is reached."},
     {ENOENT,  "The given path doesn't exist."},
     {ENOSPC,  "No space left on device."},
     {ENOTDIR, "It is not a directory."},
     {EOVERFLOW, "File size can't be represented."},
     {EROFS,   "The file system is read only."},
     {EAGAIN,  "Resource temporarily unavailable."},
     {EBADF,   "The given file descriptor is invalid."},
     {ENOMEM,  "Not enough memory."},
     {EFBIG,   "Maximum file size reached."},
     {EIO,     "Disk level I/O error occured."},
     {ENXIO,   "Device doesn't exist."}});

Status ErrorConverter::GetErrorStatusFromErrno(const string& function_name,
    const string& file_path, int err_no, const Params& params) {
  return Status(ErrorMsg(TErrorCode::DISK_IO_ERROR, GetBackendString(),
      GetErrorText(function_name, file_path, err_no, params)));
}

bool ErrorConverter::IsBlacklistableError(int err_no) {
  // A set of disk IO related non-transient error codes that should cause failure for
  // reading/writing file on local disk. These errors could be caused by incorrect
  // configurations or file system errors, but are not caused by temporarily unavailable
  // resource, like EAGAIN, ENOMEM, EMFILE, ENFILE and ENOSPC. Since these errors are not
  // likely disappear soon, the node which frequently hits such disk errors should be
  // blacklisted.
  static const set<int32_t> blacklistable_disk_error_codes = {
      EPERM, // Operation not permitted.
      ENOENT, // No such file or directory.
      ESRCH, // No such process.
      EINTR, // Interrupted system call.
      EIO, // Disk level I/O error.
      ENXIO, // No such device or address.
      E2BIG, // Argument list too long.
      ENOEXEC, // Exec format error.
      EBADF, // The given file descriptor is invalid.
      EACCES, // Permission denied.
      EFAULT, // Bad address.
      ENODEV, // No such device
      ENOTDIR, // It is not a directory.
      EINVAL, // Invalid argument.
      EFBIG, // Maximum file size reached.
      ESPIPE, // Illegal seek.
      EROFS, // The file system is read only.
      ENAMETOOLONG, // Either the path length or a path component exceeds the max length.
      EOVERFLOW}; // File size can't be represented.

  // Return true if the err_no matches any of the 'blacklistable' error code.
  return (blacklistable_disk_error_codes.find(err_no)
      != blacklistable_disk_error_codes.end());
}

bool ErrorConverter::IsBlacklistableError(const Status& status) {
  // Return true if the error is generated by Debug Action.
  // Return false if error code is not set as DISK_IO_ERROR, or there is no 'err_no' in
  // error text, or 'err_no' is set in wrong format.
  if (status.IsInternalError()) {
    return (status.msg().msg().find("Debug Action") != string::npos);
  } else if (!status.IsDiskIoError()) {
    return false;
  }

  size_t found = status.msg().msg().find("errno=");
  if (found == string::npos) return false;
  size_t start_pos = found + 6;
  size_t end_pos = status.msg().msg().find(",", start_pos);
  string value = (end_pos != string::npos) ?
      status.msg().msg().substr(start_pos, end_pos - start_pos) :
      status.msg().msg().substr(start_pos);
  if (value.empty()) return false;
  StringParser::ParseResult result;
  int err_no = StringParser::StringToInt<int32_t>(value.c_str(), value.length(), &result);
  if (result != StringParser::PARSE_SUCCESS) {
    return false;
  } else {
    return IsBlacklistableError(err_no);
  }
}

string ErrorConverter::GetErrorText(const string& function_name,
    const string& file_path, int err_no, Params params) {
  const string* error_text_body = GetErrorTextBody(err_no);
  if (error_text_body != nullptr) {
    params["errno"] = SimpleItoa(err_no);
    return Substitute("$0 failed for $1. $2 $3", function_name, file_path,
        *error_text_body, GetParamsString(params), err_no);
  }
  return Substitute("$0 failed for $1. errno=$2, description=$3", function_name,
      file_path, err_no, GetStrErrMsg(err_no));
}

string ErrorConverter::GetParamsString(const Params& params) {
  string result = "";
  bool first = true;
  for (const auto& item : params) {
    if (!first) result.append(", ");
    result.append(item.first).append("=").append(item.second);
    first = false;
  }
  return result;
}

const string* ErrorConverter::GetErrorTextBody(int err_no) {
  auto error_mapping_it = errno_to_error_text_map_.find(err_no);
  if (error_mapping_it != errno_to_error_text_map_.end()) {
    return &error_mapping_it->second;
  }
  return nullptr;
}

}
